/* input.c - various functions related to BLACC's input.
 */

#include "input.h"
#include <ctype.h>
#include "loadfile.h"
#include "javascrp.h"


typedef struct  TInput
    {
    const char* Filename;
    char*       FileText;
    char*       FileEnd;
    int         LineNumber;
    const char* Rover;
    int         Peeked;
    TToken      Token;
    int         State;
    size_t      iNextToken;
    TToken*     Master;
    size_t      MasterLen;
    size_t      MasterCap;
    int         SectionCount;       /* how many %%'s have we seen? */
    }           TInput;

TInput  Input;


TToken  EOFToken        = {TK_EOF,      "BLC_EOF",      7};
TToken  StartToken      = {TK_START,    "<start>",      7};
//TToken  EOLToken        = {TK_EOL,      "BLC_EOL",      7};
TToken  MatchAnyToken   = {TK_MATCHANY, "BLC_MATCHANY", 12};
TToken  NoActionToken   = {TK_MATCHANY, "BLC_NOACTION", 12};
TToken  ReduceToken     = {TK_IDENT, "{}", 2};
TToken  DefActToken     = {TK_IDENT, "{A0}", 4};
static
TToken      InputGetNextInternal();

/* TokenFake() - create a fake TToken.
 *
 * Sometimes it is convenient to introduce things that were not
 * explicitly a part of the input grammar. This function makes it
 * easy to create a TToken that wasn't actually in the input stream.
 *
 * Mostly just to get clean memory debugging, we track every
 * fake token we allocate so we can free it up in InputDestroy();
 */
typedef struct  TFakeList
    {
    struct TFakeList*   Next;
    char*               Text;
    }           TFakeList;

static
TFakeList*  FakeList;
TToken      TokenFake(int Type, const char* Format, ...)
    {
    TToken      Result;
    char*       Buffer;
    va_list     Args;
    TFakeList*  Item;

    Buffer      = malloc(1024 * 16);
    assert(Buffer != NULL);

    va_start(Args, Format);
    vsprintf(Buffer, Format, Args);
    va_end(Args);

    Result.Type     = Type;
    Result.TextLen  = strlen(Buffer);
    Result.Text     = realloc(Buffer, Result.TextLen+1);
    assert(Result.Text != NULL);

    /* track allocation for later freeing */
    Item    = NEW(TFakeList);
    assert(Item != NULL);
    Item->Text  = (char*)Result.Text;
    Item->Next  = FakeList;
    FakeList    = Item;
    
    return Result;
    }

int         InputIsFake(TToken Token)
    {
    return !(Token.Text >= Input.FileText && Token.Text < Input.FileEnd);
    }


int         TokenEqual(TToken A, TToken B)
    {
    int     Result  = FALSE;

    if(A.TextLen == B.TextLen)
        if(!strncmp(A.Text, B.Text, A.TextLen))
            Result  = TRUE;

    return Result;
    }


static
void     AddToken(TToken Token)
    {
    /* if we need to grow master token list */
    if(Input.MasterLen >= Input.MasterCap)
        {
        Input.MasterCap+= 510;
        Input.Master    = realloc(Input.Master, Input.MasterCap*sizeof(TToken));
        assert(Input.Master != NULL);
        }
    Input.Master[Input.MasterLen++] = Token;
    }

/* InputDestroy() - free up memory.
 *
 * Try to free up everything we can, to make the memwatch
 * log more useful.
 */
void        InputDestroy(void)
    {
    TFakeList*  Rover;
    TFakeList*  Temp;
    
    if(Input.Filename)
        free((void*)Input.Filename);
    if(Input.FileText)
        free(Input.FileText);
    if(Input.Master)
        free(Input.Master);
    for(Rover=FakeList; Rover; )
        {
        Temp    = Rover->Next;
        free(Rover->Text);
        free(Rover);
        Rover   = Temp;
        }
    }

int      InputCreate(const char* Filename)
    {
    static int  Initialized=FALSE;
    int     Errors;
    char*   FileText;

    if(!Initialized)
        {
        EOFToken        = TokenFake(TK_EOF,         "BLC_EOF");
        StartToken      = TokenFake(TK_START,       "<start>");
//        EOLToken        = TokenFake(TK_EOL,         "BLC_EOL");
        MatchAnyToken   = TokenFake(TK_MATCHANY,    "BLC_MATCHANY");
        NoActionToken   = TokenFake(TK_MATCHANY,    "BLC_MATCHANY");
        ReduceToken     = TokenFake(TK_IDENT,       "{}");
        Initialized     = TRUE;
        }
    FileText    = LoadFile(Filename);
    assert(FileText != NULL);

Dump("back from LoadFile('%s')\n", Filename);
    Input.LineNumber    = 1;
    Input.FileText      = FileText;
    Input.Rover         = FileText;
    Input.Filename      = StrClone(Filename);
    Input.FileEnd       = FileText + strlen(FileText);
    Input.State         = 0;          /* normal state (everywhere but precedence decls) */
    Input.iNextToken    = 0;
    Input.SectionCount  = 0;
    Input.MasterLen     = 0;
    Input.MasterCap     = 510;
    Input.Master        = (TToken*)malloc(Input.MasterCap*sizeof(TToken));
    assert(Input.Master != NULL);

    for(Errors=0;;)
        {
        TToken  Token = InputGetNextInternal();

        AddToken(Token);
        switch(Token.Type)
            {
            case TK_ILLEGAL:
            case TK_BADQUOTE:
                ++Errors;
                break;
            case TK_SECTION:
                if(Input.SectionCount == 2 && Input.Rover[0] != '\0')
                    Input.State = 99;
                break;
            }
        if(Token.Type == TK_EOF)
            break;
        }

Dump("InputCreate() returns %d\n", Errors);
    return Errors;
    }

static
const char* SkipWhiteSpace(const char* Rover, int NewLinesToo)
    {
    const char* Skip;
    TToken  Token;

    Token.Text  = Rover;
    
    Skip = NewLinesToo ? " \t\r\n" : " \t";
    while(*Rover && strchr(Skip, *Rover))
        ++Rover;

    if(Rover > Token.Text)
        {
        Token.TextLen   = Rover - Token.Text;
        Token.Type      = TK_WHITESPACE;
        AddToken(Token);
        }
    return Rover;
    }

static
const char* SkipCommentLine(const char* Rover)
    {
    TToken  Token;
    
    assert(Rover[0] == '/' && Rover[1] == '/');

    Token.Type  = TK_COMMENT;
    Token.Text  = Rover;
    for(Rover+= 2; *Rover; ++Rover)
        if(*Rover == '\n')
            break;
    Token.TextLen   = Rover - Token.Text;
    AddToken(Token);
    return Rover;
    }

static
const char* SkipComment(const char* Rover)
    {
    TContext    Context;
    TToken      Token;
    
    assert(Rover[0] == '/' && Rover[1] == '*');

    Context     = InputGetContext(Rover);
    Token.Type  = TK_COMMENT;
    Token.Text  = Rover;
    for(Rover+=2; *Rover; ++Rover)
        if(Rover[0] == '*' && Rover[1] == '/')
            break;
    if(*Rover)
        Rover  += 2;
    else
        {
        fprintf(stderr, "Line %d: unexpected EOF in comment that started here.\n",
                Context.LineNumber);
        Exit(Context.LineNumber, ERROR_EOF_IN_COMMENT);
        }
    Token.TextLen   = Rover - Token.Text;
    AddToken(Token);

    return Rover;
    }

/* SkipStuff() - skip whitespace and comments.
 */
static
void        SkipStuff()
    {
    const char* Rover;
    const char* OldRover;

    Rover       = Input.Rover;
    OldRover    = Rover;

    for(;;)
        {
        Rover    = SkipWhiteSpace(Rover, TRUE);
        if(Rover[0] == '/')
            {
            if(Rover[1] == '*')
                Rover    = SkipComment(Rover);
            else if(Rover[1] == '/')
                Rover    = SkipCommentLine(Rover);
            }
        if(Rover == OldRover)
            break;
        else
            OldRover    = Rover;
        }
    Input.Rover    = Rover;
    }

/* GetCPreCode() - gather up C/C++ code inside %{...%}
 *
 * ??? crude version. Rework later...
 */
static
TToken    GetCPreCode(const char** RoverPtr)
    {
    TToken      Result;
    const char* Rover       = *RoverPtr;

    Result.Text = Rover;
    ++Rover;    /* skip '{' */
    while(*Rover && (*Rover != '%' || Rover[1] != '}'))
          ++Rover;
    if(*Rover)
        {
        Result.Type     = TK_CODE;
        *RoverPtr       = Rover+2;
        Result.TextLen  = *RoverPtr - Result.Text;
        }
    else
        {
        Result.Type     = TK_ILLEGAL;
        *RoverPtr       = Rover;
        Result.TextLen  = Rover - Result.Text;
        }
    return Result;
    }

/* GetNextToken() - identify and return the next token in the input
 *
 * Note that we are always operating on an in-memory copy of the original
 * input file.
 */
static
TToken      GetNextToken()
    {
    TToken      Token;
    const char* Rover;
    int         Char;

    if(Input.State == 0)
        SkipStuff();

    Rover       = Input.Rover;

    Token.Type      = TK_NOTUSED;
    Token.Text      = Rover;    /* Should be at start of some token now */
    Token.TextLen   = 1;        /* assume it's one byte long for now    */

    if(Input.State == 0)        /* if normal token processing in effect */
        switch((Char=*Rover++))
            {
            case    '\0' :
                Token.Type  = TK_EOF;
                --Rover;
                break;
            case    '^'     :
                if(*Rover == ':')
                    {
                    ++Rover;
                    ++Token.TextLen;
                    }
            case    ':'     :
            case    '|'     :
            case    ';'     :
            case    '/'     :
                Token.Type  = Char;
                break;
            case    '@'     :
                Token.Type = TK_MATCHANY;
                break;
            default:
                if(Char == '%')
                    {
                    if(*Rover == '%')
                        {
                        Token.Type  = TK_SECTION;
                        ++Rover;
                        ++Token.TextLen;
                        ++Input.SectionCount;
                        }
                    else if(*Rover == '{')
                        {
                        Token   = GetCPreCode(&Rover);
                        }
                    else
                        {
                        while(isalpha(*Rover))
                            ++Rover;
                        Token.TextLen   = Rover - Token.Text;
                        if(Token.TextLen == 5 && !strncmp("%left", Token.Text, 5))
                            Token.Type  = TK_LEFT, Input.State = 1;
                        else if(Token.TextLen == 6 && !strncmp("%right", Token.Text, 6))
                            Token.Type  = TK_RIGHT, Input.State = 1;
                        else if(Token.TextLen == 9 && !strncmp("%nonassoc", Token.Text, 9))
                            Token.Type  = TK_NONASSOC, Input.State = 1;
                        else if(Token.TextLen == 6 && !strncmp("%token", Token.Text, 6))
                            Token.Type  = TK_TOKEN;
                        else if(Token.TextLen == 8 && !strncmp("%operand", Token.Text, 8))
                            Token.Type  = TK_OPERAND;
                        else if(Token.TextLen == 5 && !strncmp("%test", Token.Text, 5))
                            Token.Type  = TK_TEST;
                        else if(Token.TextLen == 6 && !strncmp("%start", Token.Text, 6))
                            Token.Type  = TK_START;
                        else
                            Token.Type  = TK_ILLEGAL;
                        }
                    }
    // ??? This section not done (handle comments, etc.)
                else if(Char == '{')
                    {
                    int     BraceCount  = 1;
                    int     State       = 0;
                    int     Done        = FALSE;
    
                    for(Done = FALSE; !Done && BraceCount > 0; ++Rover)
                        {
                        switch(State)
                            {
                            case    0:  /* normal, initial state */
                                switch(*Rover)
                                    {
                                    case    '\0': Done = TRUE;  break;
                                    case    '}' : --BraceCount; break;
                                    case    '\'': State = 1;    break;
                                    case    '\"': State = 2;    break;
                                    case    '/' :
                                        if(Rover[1] == '/')
                                            State = 3;
                                        else if(Rover[1] == '*')
                                            State = 4;
                                        break;
                                    }
                                break;
                            case    2: /* processing string quote */
                                switch(*Rover)
                                    {
                                    case    '\0': Done  = TRUE; break;
                                    case    '"' : State = 0;    break;
                                    case    '\\':
                                        if(Rover[1] == '"')
                                            ++Rover;
                                        break;
                                    }
                                break;
                            }
                        }
                    if(BraceCount <= 0)
                        {
                        Token.Type      = TK_ACTION;
                        Token.TextLen   = Rover - Token.Text;
                        }
                    }
                else if(Char == '\'')
                    {
                    Token.Type      = TK_QUOTED;
                    Token.TextLen   = 1;
                    while(*Rover != '\'' && *Rover != '\0' && *Rover != '\n')
                        {
                        if(*Rover == '\\')
                            {
                            if(Rover[1] == '\'')
                                ++Rover;
                            }
                        ++Rover;
                        }
                    if(*Rover == '\'')
                        {
                        ++Rover;
                        Token.TextLen   = Rover - Token.Text;
                        }
                    else
                        Token.Type  = TK_BADQUOTE;
                    }
                else if(isalpha(Char) || Char == '_' || Char == '.')
                    {
                    while(isalnum(*Rover) || *Rover == '_' || *Rover == '.')
                        ++Rover;
                    Token.Type      = TK_IDENT;
                    Token.TextLen   = Rover - Token.Text;
                    }
                else
                    Token.Type      = TK_ILLEGAL;
            }
    else if(Input.State == 1)   /* else State!=0, must be processing precedence decls */
        {
        /* skip leading white space */
        Rover   = SkipWhiteSpace(Rover, FALSE);
//        while(*Rover == ' ' || *Rover == '\t')
//            ++Rover;

        Token.Text  = Rover;
        if(*Rover == '\n')
            {
            Token.Type      = TK_NEWLINE;
            ++Rover;
            Input.State     = 0; /* back to normal processing*/
            }
        else if(*Rover == '\0')
            {
            Token.TextLen   = 0;
            Token.Type      = TK_EOF;
            Input.State     = 0; /* back to normal processing (if any!) */
            }
        else
            {
            while(!strchr(" \t\n", *Rover))
                ++Rover;
            Token.TextLen   = Rover - Token.Text;
            Token.Type      = TK_OPERATOR;
            }

        }
    else if(Input.State == 99)  /* gathering program section */
        {
        while(*Rover != '\0')
            ++Rover;
        --Rover;
        Token.TextLen   = Rover - Token.Text;
        Token.Type  = TK_PROGRAM;
        Input.State = 0;
        }
    Input.Rover    = Rover;
    
    assert(Token.Type != TK_NOTUSED);

    return Token;
    }

/* InputGetLine() - get the rest of the current input line as a token.
 *
 * The rest of the current line will be returned as though it were a
 * token. The terminating character could be carriage return, newline,
 * or EOF. Any end-of-line sequence will be skipped over, but not returned
 * as part of the token.
 */
TToken      InputGetLine()
    {
    const char* Rover;
    TToken      Result;

    /* handle case of peeked token */
    if(Input.Peeked)
        {
        Input.Peeked   = FALSE;
        Rover           = Input.Token.Text;
        }
    else
        Rover           = Input.Rover;
    Result.Text     = Rover;
    while(*Rover != '\0' && *Rover != '\r' && *Rover != '\n')
        ++Rover;
    Result.TextLen  = Rover - Result.Text;
    Result.Type     = TK_LINE;

    return Result;
    }


/* InputGetNext() - get the next token.
 *
 * The input has already been completely tokenized, so all we
 * do here is move forward in the token list until we find a token
 * that is not a comment or whitespace.
 */

TToken      InputGetNext()
    {
    int     Done;
    TToken  Result;
    
    if(Input.Peeked)   /* if we have a lookahead token ready to go */
        {
        Input.Peeked   = FALSE;
        Result          = Input.Token;
        }
    else
        {
        for(Done=FALSE; !Done;)
            {
            if(Input.iNextToken >= Input.MasterLen)
                {
                Result          = EOFToken;
                break;
                }
            else
                Result          = Input.Master[Input.iNextToken++];
            switch(Result.Type)
                {
                default: Done = TRUE;
                    break;
                case    TK_COMMENT:
                case    TK_WHITESPACE:
                    break;
                }
            }
        }

    if(Result.Type == TK_IDENT)
        {
        unsigned    iToken;
        TToken  NextToken;

        iToken  = Input.iNextToken;
        while(iToken < Input.MasterLen)
            {
            NextToken   = Input.Master[iToken++];
            if(NextToken.Type == ':')
                {
                Result.Type = TK_NONTERM;
                break;
                }
            else if(NextToken.Type == TK_COMMENT || NextToken.Type == TK_WHITESPACE)
                continue;
            else
                break;
            }
        }

    return Result;
    }

TToken*     InputGetTokens(void)
    {
    return Input.Master;
    }

static
TToken      InputGetNextInternal()
    {
    TToken      Result;

    if(Input.Peeked)   /* if we have a lookahead token ready to go */
        {
        Input.Peeked   = FALSE;
        Result          = Input.Token;
        }
    else
        Result          = GetNextToken();

#if 0
printf("'%.*s'\n", Result.TextLen, Result.Text);
#endif
    assert(Result.Text >= Input.FileText);
    return Result;
    }

TToken      InputPeekNext()
    {
    if(Input.Peeked == FALSE)
        {
        Input.Token    = InputGetNext();
        Input.Peeked   = TRUE;
        }

    return Input.Token;
    }

/* InputGetChar() - get a character from the input stream.
 *
 * Basically, this exists because the operator precedence specifications
 * are funky and lie outside the much simpler normal tokenization
 * syntax for the rest of the file. We handle any previously peeked
 * token correctly on moral grounds; I'm not sure that case even arises
 * in the code currently, though.
 */
int         InputGetChar()
    {
    int         Result  = -1;

    if(Input.Peeked)   /* if we have a lookahead token ready to go */
        {
        Result          = Input.Token.Text[0];
        ++Input.Token.Text;
        if(--Input.Token.TextLen <= 0)
            Input.Peeked   = FALSE;
        }
    else
        {
        Result      = *Input.Rover;
        if(Result != 0)
            ++Input.Rover;
        }

    assert(Result != -1);

    return Result;
    }


/* InputPrecGetNext() - get next token on a precedence directive line.
 *
 * The input tokenization rules change in a precedence directive.
 * A token at this point is any sequence that doesn't contain
 * whitespace characters.
 */
TToken      InputPrecGetNext()
    {
    const char* Rover;
    TToken      Token;

    Rover   = Input.Rover;
    /* skip leading white space */
    while(*Rover == ' ' || *Rover == '\t')
        ++Rover;
    Token.Text  = Rover;
    if(*Rover == '\n')
        {
        Token.TextLen   = 1;
        Token.Type      = TK_NEWLINE;
        ++Rover;
        }
    else if(*Rover == '\0')
        {
        Token.TextLen   = 0;
        Token.Type      = TK_EOF;
        }
    else
        {
        while(!strchr(" \t\n", *Rover))
            ++Rover;
        Token.TextLen   = Rover - Token.Text;
        Token.Type      = TK_TOKEN;
        }

    Input.Rover        = Rover;
    return Token;
    }

/* InputGetContext() - get a TContext from a pointer into the file text.
 *
 * For error messages, it's convenient to have the text of the entire line
 * the error occured in, along with the line number. This function takes
 * a pointer into the text from the input file and returns all that information
 * in a TContext structure.
 */
TContext    InputGetContext(const char* Text)
    {
    TContext    Result;
    const char* Rover;

    assert(Text != NULL);
    
    Rover               = Input.FileText;
    Result.LineNumber   = 0;
    Result.LineLen      = 0;

    if(Text >= Input.FileText && Text <= Input.FileEnd)
        {
        /* count line numbers from start to context point */
        while(Rover <= Text)
            {
            /* if previous char was a newline */
            if(Rover == Input.FileText || Rover[-1] == '\n')
                {
                ++Result.LineNumber;
                /* remember where most recent line started */
                Result.LineStart    = Rover;
                }
            if(*Rover == '\0')
                break;
            ++Rover;
            }
        /* locate the end of this line */
        while(*Rover && Rover[-1] != '\n')
            ++Rover;
    
        if(*Text == '\0')   /* if context points to EOF */
            {
            Result.LineLen  = 0;
            }
        else
            {
            assert(Rover >= Result.LineStart);
            Result.LineLen  = (Rover - Result.LineStart) - 1;       
            }
    
        }
    return Result;
    }

#if 0
void        InputError(INPUT Handle, const char* Text)
    {
    TInput*     Input   = (TInput*)Handle;
    const char* LineStart;

    assert(Handle != NULL);
    assert(Text != NULL);
    assert(Text > Input.FileText);
    LineStart   = Text;
    while(LineStart > Input.FileText && *LineStart != '\n')
        --LineStart;
    }
#endif

//int         InputGetLineNumber(INPUT Input, const char* Text);
//const char* InputGetLineStart(INPUT Input, const char* Text);




